package editor.tester;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileFilter;

import org.jwalk.LoaderException;
import org.jwalk.Settings;

import editor.Context;

/**
 * GeneratorDialog is a kind of dialog for selecting custom generators.
 * It is created when the user clicks the "Generators" button on the 
 * "Custom Settings" panel of the JWalkTester application.  It allows
 * the user to navigate the file system to set a different generator
 * location (the default is the current working directory) and then to
 * select, or browse within sub-packages, to find the desired generator.
 * Once found, the named generator is selected.  Clicking the "Add"
 * button will add it to the current list of custom generators.  Clicking
 * the "Remove" button will remove the selected generator from this list.
 * The selected generator may be changed by clicking on another generator
 * name in the displayed list of current custom generators.
 * When the "OK" button is clicked, the current list of generators is
 * uploaded to the JWalker's test Settings; and any errors are reported
 * using a pop-up JWalk Error dialog.  Clicking the "Cancel" button will
 * cause this dialog to exit without modifying any Settings.
 * 
 * @author Anthony J H Simons
 * @version JWalk 1.0
 */

@SuppressWarnings("serial")
public class GeneratorDialog extends AbstractDialog {
	private static final int DIALOG_WIDTH = 320;
	private static final int DIALOG_HEIGHT = 280;
	private static final String emptyString = "<empty>";	// Feedback!

	private File directory = null;		// directory for custom generators

	private JTextField rootField;		// custom generator directory name
	private JTextField nameField;		// selected custom generator name
	private JList generatorList;		// currently used custom generators


	/**
	 * Creates a GeneratorDiaolog for its owning JWalkTester.  The dialog has
	 * a sub-panel for locating a generator, and a sub-panel for adding and
	 * removing generators to and from the current list of custom generators.
	 * As the list grows or shrinks, this dialog resizes itself to the ideal
	 * size.  Closing the window, or clicking "Cancel" will abort this dialog.
	 * Clicking "OK" will update the generator directory and the current set
	 * of custom generators.
	 * @param tester the JWalkTester which launched this GeneratorDialog.
	 */
	public GeneratorDialog() {
		super("JWalk Custom Generators");
		// Set up basic size and appearance of this dialog
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		getRootPane().setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		setMinimumSize(new Dimension(DIALOG_WIDTH, DIALOG_HEIGHT));
		setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
		setLocationRelativeTo(Context.getJwalkEditor());
		add(createGeneratorFinderPanel());
		add(createGeneratorChooserPanel());
		add(createExitDialogPanel());
		pack();

	}

	/**
	 * Creates the panel for locating custom test value generators.  This is
	 * laid out as a 4 x 1 grid with labels and input panels on alternate
	 * rows.  The input panels are 2 x 1 grids consisting of input fields
	 * and browse-buttons.  The expected mode of operation is first to
	 * select the location, or the root directory of the package containing
	 * the generator, then to select the generator within its package.  It 
	 * is also possible to select the generator directly, if it is not part 
	 * of a package.
	 * @return the created panel.
	 */

	private JPanel createGeneratorFinderPanel() {
		Settings settings = Context.getJwalker().getSettings();
		directory = settings.getOracleDirectory();
		JPanel panel = new JPanel(new GridLayout(4, 1));
		panel.setBorder(BorderFactory.createTitledBorder("Generator"));
		// Displays the name of the generator working directory.
		rootField = new JTextField();
		rootField.setText(directory.getAbsolutePath());
		rootField.setEditable(false);
		rootField.setToolTipText("The directory for loading custom generators");
		// Allows the user to browse for a different working directory.
		JButton rootButton = new JButton("Browse");
		rootButton.setToolTipText("Browse to select the custom generator directory");
		rootButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				browseForGenerator(true);	// Seek a root directory
			}
		});
		// Create a panel for aligning the text field and button.
		JPanel rootPanel = new JPanel();
		rootPanel.setLayout(new BoxLayout(rootPanel, BoxLayout.X_AXIS));
		rootPanel.add(rootField);
		rootPanel.add(rootButton);
		panel.add(new JLabel(" Location: "));
		panel.add(rootPanel);
		// Displays the package-qualified name of the generator.
		nameField = new JTextField();
		nameField.setEditable(true);
		nameField.setToolTipText("Enter the (qualified) name of the generator");
		JButton nameButton = new JButton("Browse");
		nameButton.setToolTipText("Browse within a package for the generator");
		nameButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				browseForGenerator(false);	// Seek an actual class file
			}
		});
		// Create a panel for aligning the text field and button.
		JPanel namePanel = new JPanel();
		namePanel.setLayout(new BoxLayout(namePanel, BoxLayout.X_AXIS));    
		namePanel.add(nameField);
		namePanel.add(nameButton);
		panel.add(new JLabel(" Name: "));
		panel.add(namePanel);
		return panel;  
	}

	/**
	 * Creates the panel for selecting which custom generators are used.  
	 * This is a variable-height BoxLayout that consists of a sub-panel with
	 * the Add and Remove buttons, followed by a sub-panel containing a
	 * variable-length JList, which displays the user's choice of custom
	 * generators.  The buttons refer to the currently selected generator in
	 * the finder-panel (not this panel).  Selecting a generator from those 
	 * displayed in the JList makes this the currently-selected generator.
	 * @return the created panel.
	 */
	private JPanel createGeneratorChooserPanel() {
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
		panel.setBorder(BorderFactory.createTitledBorder("Custom Generators"));
		// Button panel has a flow layout to pack buttons centrally.
		JPanel addRemovePanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
		JButton addButton = new JButton("Add");
		addButton.setToolTipText(
		"Add the selected generator to the custom generators");
		addButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				addGenerator(nameField.getText());
			}
		});
		JButton removeButton = new JButton("Remove");
		removeButton.setToolTipText(
		"Remove the selected generator from the custom generators");
		removeButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				removeGenerator(nameField.getText());
			}
		});
		addRemovePanel.add(addButton);
		addRemovePanel.add(removeButton);
		panel.add(addRemovePanel);
		// List panel has a grid layout to ensure full-width display of list.
		JPanel listPanel = new JPanel(new GridLayout(1, 1));
		listPanel.setBorder(BorderFactory.createEtchedBorder());
		DefaultListModel model = new DefaultListModel();
		model.addElement(emptyString);		// Dummy string for user feedback!
		generatorList = new JList(model);
		generatorList.setBackground(Color.WHITE);
		generatorList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		Settings settings = Context.getJwalker().getSettings();
		for (Class<?> generator : settings.getCustomGenerators()) {
			// Use local addGenerator to eliminate emptyString correctly.
			addGenerator(generator.getName());
		}
		generatorList.setModel(model);
		generatorList.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent event) {
				// Note: respond only to the last of two selection events
				if (! event.getValueIsAdjusting())
					selectGenerator();
			}
		});
		listPanel.add(generatorList);
		panel.add(listPanel);
		return panel;
	}
	
	/**
	 * Browse the file system to find a generator.  This is a complex
	 * operation, which supports finding the generator in one or two
	 * steps.  If the generator is not in a package, then it may be 
	 * selected in a single step (whether isRoot is true or false).  If
	 * the generator is in a package, the root location must be selected
	 * first (with isRoot set to true) and the generator selected within
	 * the package (with isRoot set to false).
	 * @param isRoot a flag indicating whether the intention is to set
	 * the root working directory.
	 */

	private void browseForGenerator(boolean isRoot) {
		JFileChooser chooser = new JFileChooser(directory);
		chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
		chooser.setFileFilter(new FileFilter() {
			public boolean accept(File f) {
				return f.isDirectory() || 
				f.getName().endsWith("Generator.class");
			}
			public String getDescription() {
				return "Folders and JWalk generators";
			}
		});
		int result = chooser.showOpenDialog(this);
		if (result == JFileChooser.APPROVE_OPTION) {
			File selected = chooser.getSelectedFile();
			if (directory == null || isRoot) {
				if (selected.isDirectory())
					directory = selected;
				else
					directory = chooser.getCurrentDirectory();
			}
			String dirName = directory.getAbsolutePath();
			String clsName = selected.getAbsolutePath();

			if (selected.isFile() && clsName.contains(dirName)) {
				clsName = clsName.substring(dirName.length() + 1);
				clsName = clsName.substring(0, clsName.indexOf(".class"));
			}
			else {
				clsName = "";
			}
			rootField.setText(dirName);
			nameField.setText(clsName.replace(File.separatorChar, '.'));
		}
	}

	/**
	 * Adds the selected generator name to the list.  The name must end 
	 * in "Generator" to be a valid name, otherwise it is ignored.  If the
	 * list is empty, the dummy "<empty>" value is removed.  If the list 
	 * already contains this generator name, it is not added again.  
	 * When this operation completes, the dialog is resized to accommodate
	 * the new list entry and is packed to the ideal dimensions.
	 * @param name the selected generator name.
	 */
	private void addGenerator(String name) {
		if (name.endsWith("Generator")) {
			DefaultListModel model = (DefaultListModel) generatorList.getModel();
			if (model.contains(emptyString))
				model.removeElement(emptyString);
			if (! model.contains(name))
				model.addElement(name);
			generatorList.setModel(model);
			pack();
		}
	}

	/**
	 * Removes the selected generator name from the list.  The name must
	 * end in "Generator" to be a valid name, otherwise it is ignored.  If the
	 * last entry is removed from the list, the dummy "<empty>" string is added
	 * again.  If the list does not contain this generator name, no action is
	 * taken.  When this operation completes, the dialog is resized to reflect
	 * one fewer list entries and is packed to the ideal dimensions.
	 */
	private void removeGenerator(String name) {
		if (name.endsWith("Generator")) {
			DefaultListModel model = (DefaultListModel) generatorList.getModel();
			model.removeElement(name);
			if (model.size() == 0)
				model.addElement(emptyString);
			generatorList.setModel(model);
			pack();
		}
		
		save();
		
	}

	/**
	 * Changes the currently selected generator.  Clicking in the display list
	 * of generators in the chooser-panel causes the selected generator in the
	 * finder-panel to be changed.  Once selected, a generator may be removed
	 * from the list.  If the selection is the dummy "<empty>" string, takes
	 * no action.
	 */
	private void selectGenerator() {
		int index = generatorList.getSelectedIndex();
		// Disregard selection events that de-select, returning -1.
		if (index != -1) {
			DefaultListModel model = (DefaultListModel) generatorList.getModel();
			String selection = (String) model.get(index);
			if (selection != emptyString) {
				nameField.setText(selection);
			}
		}
	}

	/**
	 * Uploads the current custom generator list to the JWalker's settings.
	 * For each new custom generator named in the list, seeks to upload this 
	 * to the JWalker's test settings.  For each dropped custom generator, 
	 * removes this from the JWalker's settings.  If a given generator is in 
	 * both the old and new lists, no action is taken for that generator.
	 * If any one of the new generators cannot be loaded, because of a wrong
	 * name, or incorrect package access, this action is aborted and a pop-up
	 * JWalk Error dialog warns the user.  Note that some generators may have
	 * already been uploaded before any abort is signalled; and no generators
	 * are removed until all uploads have succeeded.
	 * @param okClicked true, if the "OK" button was clicked; false if the 
	 * "Cancel" button was clicked.
	 */
	protected void saveSettings(boolean okClicked) {
		boolean closeOnExit = true;
		if (okClicked) {
			closeOnExit = save();
		}
		if (closeOnExit) {
			setVisible(false);
			dispose();
		}
		
		System.out.println("A" + Context.getJwalker().getSettings().getCustomGenerators());
	}
	
	private boolean  save()
	{
		boolean closeOnExit = true;
		// Change the current generator root directory.
		Settings settings =  Context.getJwalker().getSettings();
		settings.setGeneratorDirectory(directory);
		// Filter the list of new Generator names.
		DefaultListModel model = (DefaultListModel) generatorList.getModel();
		List<String> newNames = new ArrayList<String>();
		for (Object object : model.toArray()) {
			if (object != emptyString)
				newNames.add((String) object);
		}
		// Collect the list of old Generator names.
		List<String> oldNames = new ArrayList<String>();
		for (Class<?> generator : settings.getCustomGenerators())
			oldNames.add(generator.getName());
		// Remove the unused old generators from the Settings.
		for (String oldName : oldNames)
			if (! newNames.contains(oldName))
				settings.removeCustomGenerator(oldName);
		// Try to add the additional new generators to the Settings.
		try {
			for (String newName : newNames)
				if (! oldNames.contains(newName))
					settings.addCustomGenerator(newName);
		}
		catch (LoaderException ex) {
			closeOnExit = false;
			handleLoaderException(ex);
		}
		
		return closeOnExit;
	}
	
	/**
	 * Create a JWalk Error dialog reporting failure to load a generator.
	 * This distinguishes not finding the generator, from trying to load it
	 * using the wrong package-qualified name.
	 * @param ex the LoaderException.
	 */
	private void handleLoaderException(LoaderException ex) {
		if (ex.classNotFound())
			JOptionPane.showMessageDialog(Context.getJwalkEditor(),
					("A custom generator could not be found. \n" +
							"Please check each generator's location and \n" +
					"the spelling of each generator's name. "),
					"JWalk Error",
					JOptionPane.ERROR_MESSAGE
			);
		if (ex.classNotQualified())
			JOptionPane.showMessageDialog(Context.getJwalkEditor(),
					("A custom generator could not be loaded. \n" +
							"Please change the location and load each \n" +
					"generator using its package-qualified name. "),
					"JWalk Error",
					JOptionPane.ERROR_MESSAGE
			);
		// else do nothing.
		logException(ex);
	}

}
